package main

import (
	"crypto/x509"
	"encoding/pem"
	"fmt"
	jwt "github.com/golang-jwt/jwt/v4"
	jsoniter "github.com/json-iterator/go"
	"github.com/pkg/errors"
	"io/ioutil"
	"moonton.net/iaptool/common"
	"moonton.net/iaptool/tokens"
	"net/http"
	"time"
)

var (
	APPLE_JWT_CERT = "./config/AppleRootCA-G3.pem"
)

type AppleNotifyPayloadV2 struct {
	NotificationType string `json:"notificationType"`
	Subtype          string `json:"subtype"`
	// 通知的唯一标识符。使用此值来识别重复通知。
	NotificationUUID string              `json:"notificationUUID"`
	Data             AppleNotifyMetaData `json:"data"`
	Version          string              `json:"version"`
	jwt.StandardClaims
}

type AppleNotifyMetaData struct {
	// 应用的唯一标识符。此属性适用于从 App Store 下载的应用程序；它不存在于沙盒环境中。
	AppAppleId int `json:"appAppleId"`
	// 应用程序的捆绑标识符。
	BundleId string `json:"bundleId"`
	// 标识捆绑包迭代的构建版本
	BundleVersion string `json:"bundleVersion"`
	// Sandbox 沙盒环境中的测试, Production 生产环境。
	Environment string `json:"environment"`
	// 由App Store签名的订阅续订信息，需要再次验证jws
	SignedRenewalInfo *string `json:"signedRenewalInfo"`
	//RenewalInfo *AppleNotifyRenewalInfo
	// 由App Store签名的交易信息，需要再次验证jws
	SignedTransactionInfo *string `json:"signedTransactionInfo"`
	//TransactionInfo *AppleNotifyTransactionInfo
}
type AppleNotifyMetaDataInner struct {
	NotificationType string `json:"notificationType"`
	Subtype          string `json:"subtype"`
	AppAppleId       int    `json:"appAppleId"`
	BundleId         string `json:"bundleId"`
	BundleVersion    string `json:"bundleVersion"`
	Environment      string `json:"environment"`
	RenewalInfo      *AppleNotifyRenewalInfo
	TransactionInfo  *AppleNotifyTransactionInfo
}

type AppleNotifyRenewalInfo struct {
	AutoRenewProductId string `json:"autoRenewProductId"`
	AutoRenewStatus    int    `json:"autoRenewStatus"`
	Environment        string `json:"environment"`
	//1 客户取消了订阅。 2 计费错误；例如，客户的付款信息不再有效。
	//3 客户不同意需要客户同意的自动续订订阅价格上涨，从而导致订阅过期。 4 该产品在续订时无法购买。
	ExpirationIntent            int    `json:"expirationIntent"`
	GracePeriodExpiresDate      int64  `json:"gracePeriodExpiresDate"`
	OriginalTransactionId       string `json:"originalTransactionId"`
	ProductId                   string `json:"productId"`
	RecentSubscriptionStartDate int64  `json:"recentSubscriptionStartDate"`
	SignedDate                  int64  `json:"signedDate"`
	jwt.StandardClaims
}
type AppleNotifyTransactionInfo struct {
	AppAccountToken             string `json:"appAccountToken"`
	BundleId                    string `json:"bundleId"`
	ExpiresDate                 int64  `json:"expiresDate"`
	Environment                 string `json:"environment"`
	OriginalPurchaseDateMs      int64  `json:"originalPurchaseDate"`
	OriginalTransactionId       string `json:"originalTransactionId"`
	ProductId                   string `json:"productId"`
	PurchaseDateMs              int64  `json:"purchaseDate"`
	Quantity                    int    `json:"quantity"`
	RevocationDate              int64  `json:"revocationDate"`
	RevocationReason            int    `json:"revocationReason"`
	SubscriptionGroupIdentifier string `json:"subscriptionGroupIdentifier"`
	TransactionId               string `json:"transactionId"`
	WebOrderLineItemId          string `json:"webOrderLineItemId"`
	// Auto-Renewable Subscription 自动续订订阅。
	// Non-Consumablen 非消耗性应用内购买。
	// Consumable 消耗品应用内购买。
	// Non-Renewing Subscription 非续订订阅。
	Type string `json:"type"`
	jwt.StandardClaims
}

func checkCerts(x5cField []string, certFile string) (interface{}, error) {
	var pems []string
	for _, x5c := range x5cField {
		pemData := "-----BEGIN CERTIFICATE-----\n"
		for i := 0; i < len(x5c); i += 64 {
			end := i + 64
			if end > len(x5c) {
				end = len(x5c)
			}
			pemData += x5c[i:end] + "\n"
		}
		pemData += "-----END CERTIFICATE-----"
		pems = append(pems, pemData)
	}

	var certs []*x509.Certificate
	rootPem, err := ioutil.ReadFile(certFile)
	if err != nil {
		return nil, err
	}
	pems = append(pems, string(rootPem))

	for _, pemData := range pems {
		// Parse PEM block
		var block *pem.Block
		if block, _ = pem.Decode([]byte(pemData)); block == nil {
			return nil, errors.New("invalid pem format")
		}

		// Parse the key
		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			return nil, err
		}
		certs = append(certs, cert)
	}
	if len(certs) == 0 {
		return nil, errors.New("invalid x5c")
	}
	// 校验证书链
	for i := 0; i+1 < len(certs); i++ {
		if err := certs[i].CheckSignatureFrom(certs[i+1]); err != nil {
			return nil, err
		}
	}

	/*publicKey, err := jwt.ParseECPublicKeyFromPEM([]byte(cer))
	  if err != nil {
	      return err
	  }*/
	return certs[0].PublicKey, nil
}

func VerifyJWSPayload(payload string, certFile string, claims jwt.Claims) (*jwt.Token, error) {
	token, err := jwt.ParseWithClaims(payload, claims, func(token *jwt.Token) (interface{}, error) {
		// header alg: ES256
		if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
			return nil, errors.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		// header x5c: ["A","B","C"]
		x5c, ok := token.Header["x5c"]
		if !ok {
			return nil, errors.Errorf("header x5c need set, token:%+v", token)
		}

		x5cArr, ok := x5c.([]interface{})
		if !ok {
			return nil, errors.Errorf("header x5c isn't array, token:%+v\", token")
		}

		var x5cArrStr []string
		for _, v := range x5cArr {
			s, ok := v.(string)
			if !ok {
				return nil, errors.Errorf("header x5c isn't string array, token:%+v\", token")
			}
			x5cArrStr = append(x5cArrStr, s)
		}
		// 校验证书有效性，并返回公钥
		publicKey, err := checkCerts(x5cArrStr, certFile)
		if err != nil {
			return nil, err
		}

		return publicKey, nil
	})
	if err != nil {
		return nil, errors.Errorf("parser error,err=%s, SignedPayload=%s", err, payload)
	}
	if !token.Valid {
		return nil, errors.Errorf("invalid token%+v , SignedPayload=%s", token, payload)
	}
	return token, nil
	//claims, ok := token.Claims.(jwt.MapClaims)
	//if !(ok && token.Valid) {
	//	return nil, errors.Errorf("invalid token%+v , SignedPayload=%s", token, payload)
	//}
	//
	//return claims, nil
}

func sendAppleNotification(isSandbox bool) (nid string, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	sanboxStr := ""
	if isSandbox {
		sanboxStr = "-sandbox"
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
		},
		ApiUrl: fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/notifications/test", sanboxStr),
		Method: "POST",
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}

	var rspJson struct {
		NotificationID string `json:"testNotificationToken"`
	}
	err = jsoniter.Unmarshal(rspData, &rspJson)
	if err != nil {
		return
	}

	return rspJson.NotificationID, nil
}

func getAppleNotificationResult(nid string, isSandbox bool) (result string, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	sanboxStr := ""
	if isSandbox {
		sanboxStr = "-sandbox"
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
		},
		ApiUrl: fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/notifications/test/%s", sanboxStr, nid),
		Method: "GET",
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}
	var rspJson struct {
		FirstSendAttemptResult string `json:"firstSendAttemptResult"`
		SignedPayload          string `json:"signedPayload"`
	}
	err = jsoniter.Unmarshal(rspData, &rspJson)
	if err != nil {
		return
	}

	return rspJson.FirstSendAttemptResult, nil
}

// https://developer.apple.com/documentation/appstoreserverapi/get_notification_history
// paginationToken
type NotificationHistoryRequest struct {
	//  180 天内的时间跨度的开始日期。
	StartDate             int64  `json:"startDate"`
	EndDate               int64  `json:"endDate"`
	OriginalTransactionId string `json:"originalTransactionId,omitempty"`
	NotificationType      string `json:"notificationType,omitempty"`
	NotificationSubtype   string `json:"notificationSubtype,omitempty"`
}

func getAppleNotificationHistory(paginationToken *string, isSandbox bool) (payloads []string, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	req := NotificationHistoryRequest{
		StartDate: time.Now().Add(time.Hour*-10*24).UnixNano() / 1e6,
		EndDate:   time.Now().UnixNano() / 1e6,
		//NotificationType: "DID_RENEW",
		//NotificationType: "SUBSCRIBED",
	}
	reqJson, err := jsoniter.Marshal(req)
	if err != nil {
		return
	}
	sanboxStr := ""
	if isSandbox {
		sanboxStr = "-sandbox"
	}
	var apiUrl string
	if paginationToken != nil {
		apiUrl = fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/notifications/history?paginationToken=%s", sanboxStr, *paginationToken)
	} else {
		apiUrl = fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/notifications/history", sanboxStr)
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
			"Content-Type":  []string{"application/json"},
		},
		ApiUrl: apiUrl,
		Method: "POST",
		Body:   &reqJson,
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}
	var rspJson struct {
		NotificationHistory []struct {
			FirstSendAttemptResult string `json:"paginationToken"`
			SignedPayload          string `json:"signedPayload"`
		} `json:"notificationHistory"`
		HasMore         bool   `json:"hasMore"`
		PaginationToken string `json:"paginationToken"`
	}
	//fmt.Printf("-----%+v\n", string(rspData))
	err = jsoniter.Unmarshal(rspData, &rspJson)
	if err != nil {
		return
	}
	for _, payload := range rspJson.NotificationHistory {
		payloads = append(payloads, payload.SignedPayload)
	}

	if rspJson.HasMore != true {
		return
	}

	tmp, err := getAppleNotificationHistory(&rspJson.PaginationToken, true)
	if err == nil {
		payloads = append(payloads, tmp...)
	}
	return
}

func parseAppleNotify(signedPayload string, cretFile string, notifications map[string]string) (err error) {
	token, err := VerifyJWSPayload(signedPayload, cretFile, &AppleNotifyPayloadV2{})
	if err != nil {
		return
	}
	payload, ok := token.Claims.(*AppleNotifyPayloadV2)
	if !ok {
		return errors.Errorf("解析AppStore通知失败, %+v, %v", token, signedPayload)
	}

	var transactionInfo *AppleNotifyTransactionInfo
	if payload.Data.SignedTransactionInfo != nil {
		token, err = VerifyJWSPayload(*payload.Data.SignedTransactionInfo, cretFile, &AppleNotifyTransactionInfo{})
		if err != nil {
			return errors.Errorf("解析AppStore通知失败，%s, %+v, %s", err, signedPayload, *payload.Data.SignedTransactionInfo)
		}
		transactionInfo, ok = token.Claims.(*AppleNotifyTransactionInfo)
		if !ok {
			return errors.Errorf("解析AppStore通知失败, %+v, %+v, %s", token, signedPayload, *payload.Data.SignedTransactionInfo)
		}
	}

	var renewalInfo *AppleNotifyRenewalInfo
	if payload.Data.SignedRenewalInfo != nil {
		token, err = VerifyJWSPayload(*payload.Data.SignedRenewalInfo, cretFile, &AppleNotifyRenewalInfo{})
		if err != nil {
			return errors.Errorf("解析AppStore通知失败，%s, %v, %s", err, signedPayload, *payload.Data.SignedRenewalInfo)
		}
		renewalInfo, ok = token.Claims.(*AppleNotifyRenewalInfo)
		if !ok {
			return errors.Errorf("解析AppStore通知失败, %+v, %v, %s", token, signedPayload, *payload.Data.SignedRenewalInfo)
		}
	}

	metaData := &AppleNotifyMetaDataInner{
		NotificationType: payload.NotificationType,
		Subtype:          payload.Subtype,
		AppAppleId:       payload.Data.AppAppleId,
		BundleId:         payload.Data.BundleId,
		BundleVersion:    payload.Data.BundleVersion,
		Environment:      payload.Data.Environment,
		RenewalInfo:      renewalInfo,
		TransactionInfo:  transactionInfo,
	}
	notifyData, err := jsoniter.MarshalToString(metaData)
	if err != nil {
		return
	}
	if transactionInfo != nil {
		//fmt.Printf("notification:%s, payload:%s\n", transactionInfo.TransactionId, signedPayload)
		notifications[transactionInfo.TransactionId] = notifyData
	} else {
		fmt.Printf("nil transactionInof:%+v, %+v\n", payload, metaData)
	}

	return
}

func getAppleReceiptByUserOrder(userOrder string, isSandbox bool) (result string, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	sanboxStr := ""
	if isSandbox {
		sanboxStr = "-sandbox"
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
		},
		ApiUrl: fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/history/%s", sanboxStr, userOrder),
		Method: "GET",
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}
	fmt.Println("-----------")
	fmt.Println(string(rspData))
	fmt.Println("-----------")
	return
	//var rspJson struct {
	//	FirstSendAttemptResult string `json:"firstSendAttemptResult"`
	//	SignedPayload          string `json:"signedPayload"`
	//}
	//err = jsoniter.Unmarshal(rspData, &rspJson)
	//if err != nil {
	//	return
	//}

	//return rspJson.FirstSendAttemptResult, nil
}

func getAppleReceiptInfoByUserOrder(userOrder string, isSandbox bool) (result string, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	sanboxStr := ""
	if isSandbox {
		sanboxStr = "-sandbox"
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
		},
		ApiUrl: fmt.Sprintf("https://api.storekit%s.itunes.apple.com/inApps/v1/transactions/%s", sanboxStr, userOrder),
		Method: "GET",
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}
	fmt.Println("-----------")
	fmt.Println(string(rspData))
	fmt.Println("-----------")
	return
	//var rspJson struct {
	//	FirstSendAttemptResult string `json:"firstSendAttemptResult"`
	//	SignedPayload          string `json:"signedPayload"`
	//}
	//err = jsoniter.Unmarshal(rspData, &rspJson)
	//if err != nil {
	//	return
	//}

	//return rspJson.FirstSendAttemptResult, nil
}

// https://developer.apple.com/documentation/appstoreserverapi/jwstransactiondecodedpayload
type TransactionDecodedPayload struct {
	AppAccountToken             string `json:"appAccountToken"`
	BundleId                    string `json:"bundleId"`
	Environment                 string `json:"environment"`
	ExpiresDate                 int64  `json:"expiresDate"`
	OriginalPurchaseDate        int64  `json:"originalPurchaseDate"`
	OriginalTransactionId       string `json:"originalTransactionId"`
	ProductId                   string `json:"productId"`
	PurchaseDate                int64  `json:"purchaseDate"`
	Quantity                    int    `json:"quantity"`
	RevocationDate              int64  `json:"revocationDate"`
	RevocationReason            int    `json:"revocationReason"`
	Storefront                  string `json:"storefront"`
	storefrontId                string `json:"storefrontId"`
	SubscriptionGroupIdentifier string `json:"subscriptionGroupIdentifier"`
	TransactionId               string `json:"transactionId"`
	transactionReason           string `json:"transactionReason"`
	WebOrderLineItemId          string `json:"webOrderLineItemId"`
	// Auto-Renewable Subscription 自动续订订阅。
	// Non-Consumablen 非消耗性应用内购买。
	// Consumable 消耗品应用内购买。
	// Non-Renewing Subscription 非续订订阅。
	Type string `json:"type"`
	jwt.StandardClaims
}

func parseAppleLookupOrders(signedPayload string, cretFile string) (err error, payload *TransactionDecodedPayload) {
	token, err := VerifyJWSPayload(signedPayload, cretFile, &TransactionDecodedPayload{})
	if err != nil {
		return
	}
	payload, ok := token.Claims.(*TransactionDecodedPayload)
	if !ok {
		return errors.Errorf("解析AppStore通知失败, %+v, %v", token, signedPayload), nil
	}
	//fmt.Printf("%+v\n", payload)
	return
}
func LookupAppleReceiptInfoByUserOrder(userOrder string) (payloads []*TransactionDecodedPayload, err error) {
	token, err := tokens.GetCacheToken("ios")
	if err != nil {
		return
	}
	helper := common.HttpHelper{
		Header: http.Header{
			"Authorization": []string{fmt.Sprintf("Bearer %s", token)},
		},
		ApiUrl: fmt.Sprintf("https://api.storekit.itunes.apple.com/inApps/v1/lookup/%s", userOrder),
		Method: "GET",
	}
	rspData, err := helper.Do()
	if err != nil {
		return
	}
	var rspJson struct {
		Status             int32     `json:"status"`
		SignedTransactions *[]string `json:"signedTransactions,omitempty"`
	}
	//fmt.Println(string(rspData))
	err = jsoniter.Unmarshal(rspData, &rspJson)
	if err != nil {
		return
	}

	if rspJson.Status != 0 || rspJson.SignedTransactions == nil || len(*rspJson.SignedTransactions) == 0 {
		return nil, nil
	}

	for _, payload := range *rspJson.SignedTransactions {
		err, transaction := parseAppleLookupOrders(payload, APPLE_JWT_CERT)
		if err != nil {
			fmt.Printf("一个订单解析失败:%v\n, paylod:%s", err.Error(), payload)
			continue
		}
		payloads = append(payloads, transaction)
	}
	return
}
